/*
 * TowerBase.java
 *
 * Created on November 24, 2007, 3:19 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.tower;

import hextd.UI.ScaledCanvas;
import hextd.hex.Hex;
import hextd.creep.Creep;
import hextd.core.Context;
import hextd.core.ContextNormal;
import hextd.core.ContextPlayer;
import hextd.util.ParameterUtils;
import hextd.util.TowerChangeListener;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ArrayList;

/**
 *
 * @author hylke
 */
public abstract class TowerBase implements Tower {

	private int owner = 0;
	protected float paintScale, strokeScale, hexPaintWidth, offsetX, offsetY;
	protected Context context;
	protected ContextNormal cn;
	private TowerFactory.type type;
	private TowerFireControl fc;
	protected ArrayList<TowerUpgradeDamage> upgradeDamage;
	protected ArrayList<TowerUpgradeRange> upgradeRange;
	private ArrayList<TowerChangeListener> towerChangeListeners;
	//
	protected int mode = 0;
	protected String[] modes = {"Close", "Hard", "Weak", "Furthest"};
	protected boolean retarget = false;
	protected boolean lock = true;
	//
	protected Hex hex;
	protected int[] hexLocation;
	protected int hexInfluence = 2;
	protected float xFloat, yFloat;
	protected float xPaint, yPaint;
	protected int level = 0;
	protected int levelMax = 9;
	protected int levelInQue = 0;
	protected float rangeBase = 0;
	protected float rangeCurrent = 0;
	protected float rangeCurrent2 = 0;
	protected float rangePaint = 0;
	protected int damageBase = 0;
	protected long damageCurrent = 0;
	protected String damageText = "";
	protected String rangeText = "";
	//
	public static DecimalFormat form = new DecimalFormat("#,###.##");
	protected PanelTowerSettings pts;
	//
	protected Color lineColor;
	protected Color rangeColor;
	protected Shape[] shapes;
	protected int[] shapeAction;
	protected Color[] shapeColor;
	protected Stroke[] strokes;
	protected Shape rangeCircle;
	protected Stroke rangeStroke;
	protected Font levelFont;
	protected float[] levelLocation;
	protected AffineTransform atTranslate = new AffineTransform();
	private AffineTransform atCorrect;
	private boolean bodyUsesSVG;
	private String bodySVGName;
	private float bodyScale;
	//
	protected boolean selected = false;
	protected boolean selling = false;
	private boolean willSell = false;
	private int price = 0;
	//
	protected boolean active = false;

	protected class TowerAction {

		public int gameTime;
		public String action;

		public TowerAction(int gameTime, String action) {
			this.gameTime = gameTime;
			this.action = action;
		}
	}

	protected class TowerActionMode extends TowerAction {

		public int mode;
		public int value;

		public TowerActionMode(int gameTime, int mode, int value) {
			super(gameTime, "mode");
			this.mode = mode;
			this.value = value;
		}
	}
	private ArrayList<TowerAction> actionQueue;
	private Shape[] queueShapes;
	public static Color halfWhite = new Color(255, 255, 255, 64);
	private static Comparator<Creep> compareFast = new CreepCompare.CompareFast();
	private static Comparator<Creep> compareHard = new CreepCompare.CompareHard();
	private static Comparator<Creep> compareWeak = new CreepCompare.CompareWeak();
	private static Comparator<Creep> compareFar = new CreepCompare.CompareFurthest();

	/**
	 * Creates a new instance of TowerBase
	 */
	public TowerBase(TowerFactory.type t, int price, int damage, float range, int owner, int activateTick) {
		this.owner = owner;
		this.price = price;
		this.type = t;
		this.damageBase = damage;
		this.rangeBase = range;
		this.levelLocation = new float[2];
		this.upgradeDamage = new ArrayList<TowerUpgradeDamage>();
		this.upgradeRange = new ArrayList<TowerUpgradeRange>();
		this.towerChangeListeners = new ArrayList<TowerChangeListener>();
		this.actionQueue = new ArrayList<TowerBase.TowerAction>();
		this.actionQueue.add(new TowerAction(activateTick, "activate"));
	}

	protected void doInit(Context context, Hex hex) {
		this.context = context;
		this.damageCurrent = this.damageBase;

		this.hex = hex;
		if (hex != null) {
			this.hexLocation = hex.getLocation();
			hex.setTower(this);
		}

		atCorrect = new AffineTransform();
		setLocation();
		calcDamageRange();
		resetScale();
	}

	@Override
	public final void doTick(int gameTime) {
		this.checkQueue(gameTime);
		if (this.active) {
			this.runTick(gameTime);
		}
	}

	@Override
	public boolean isActive() {
		return active;
	}

	abstract protected void runTick(int gameTime);

	protected void addActionItem(TowerAction item) {
		this.actionQueue.add(item);
	}

	protected void checkQueue(int gameTime) {
		for (Iterator<TowerAction> i = this.actionQueue.iterator(); i.hasNext();) {
			TowerAction a = i.next();
			if (a.gameTime == gameTime) {
				this.handleAction(a);
				i.remove();
			}
		}
	}

	protected void handleAction(TowerAction a) {
		if (a.action.equals("activate")) {
			runActivate();
			return;
		}
		if (a.action.equals("upgrade")) {
			runUpgrade();
			return;
		}
		if (a.action.equals("mode")) {
			TowerActionMode am = (TowerActionMode) a;
			runMode(am.mode, am.value);
			return;
		}
		if (a.action.equals("sell")) {
			runSell();
			return;
		}
		System.err.println("TowerBase::handleAction: Unknown action: " + a.action + "!");
	}

	protected boolean runActivate() {
		ContextPlayer player = context.getPlayer(getOwner());
		if (player.doPay(price)) {
			hex.addClaim(owner, level + 1, hexInfluence);
			active = true;
			return true;
		} else {
			System.err.println("TowerBase::handleAction newTower: ERROR! " + context.getGameTime().getTime() + " User " + owner +
					" can not activate new tower of type " + type.toString() +
					" at " + hexLocation[0] + "," + hexLocation[1]);
			hex.unSetTower();
			doCleanup();
			context.removeTower(this);
			return false;
		}
	}

	@Override
	public int getOwner() {
		return this.owner;
	}

	@Override
	public boolean setMode(int activateTick, int m, int v) {
		this.actionQueue.add(new TowerActionMode(activateTick, m, v));
		return true;
	}

	protected boolean runMode(int m, int v) {
		if (m == 0) {
			lock = (v == 1);
			int lockV = getMode(0);
			if (pts != null && pts.getMode(m) != lockV) {
				pts.setMode(m, lockV, true);
			}
			return true;
		} else if (m == 1) {
			mode = ParameterUtils.forceIndexBounds(modes, v);
			retarget = true;
			if (pts != null && pts.getMode(m) != mode) {
				pts.setMode(m, mode, true);
			}
			return true;
		} else {
			return false;
		}
	}

	@Override
	public int getMode(int m) {
		if (m == 0) {
			if (this.lock) {
				return 1;
			} else {
				return 0;
			}
		} else if (m == 1) {
			return this.mode;
		} else {
			return -1;
		}
	}

	@Override
	public float getRangeBase() {
		return this.rangeBase;
	}

	@Override
	public float getRangeCurrent() {
		return this.rangeCurrent;
	}

	@Override
	public int getUpgradePrice(boolean future) {
		if (future) {
			if (level + levelInQue >= levelMax) {
				return -1;
			}
			return Math.round(0.5f * this.price);
		} else {
			if (level >= levelMax) {
				return -1;
			}
			return Math.round(0.5f * this.price);
		}

	}

	@Override
	public int getSellPrice() {
		return Math.round(0.75f * (1.0f + 0.5f * this.level) * this.price);
	}

	@Override
	public boolean canSell() {
		if (this.willSell) {
			return false;
		}
		return true;
	}

	@Override
	public void queueSell(int activateTick) {
		if (!this.willSell) {
			this.willSell = true;
			this.actionQueue.add(new TowerAction(activateTick, "sell"));
		}
	}

	private void runSell() {
		ContextPlayer player = context.getPlayer(getOwner());
		player.doReceive(getSellPrice());
		hex.unSetTower();
		hex.addClaim(owner, -level - 1, hexInfluence);
		doCleanup();
		context.removeTower(this);
	}

	@Override
	public void cancelBuild() {
		if (!active) {
			selling = true;
			ContextPlayer player = context.getPlayer(getOwner());
			player.unQueuePayment(price);
			if (levelInQue > 0) {
				player.unQueuePayment(levelInQue * price / 2);
			}
			hex.unSetTower();
			doCleanup();
			context.removeTower(this);
		}
	}

	@Override
	public boolean canUpgrade() {
		return (level + levelInQue < levelMax);
	}

	@Override
	public void queueUpgrade(int activateTick) {
		this.actionQueue.add(new TowerAction(activateTick, "upgrade"));
		this.levelInQue++;
	}

	protected void runUpgrade() {
		ContextPlayer player = context.getPlayer(this.getOwner());
		if (player.doPay(getUpgradePrice(false))) {
			level++;
			levelInQue--;
			damageCurrent = (int) (damageBase * (float) Math.pow(1.45, level));
			hex.addClaim(owner, 1, hexInfluence);
			calcDamageRange();
			fireTowerChangedEvent();
		} else {
			int[] loc = hex.getLocation();
			System.err.println("TowerBase::runUpgrade: " + context.getGameTime() + " User " + getOwner() + " can not upgrade tower at " + loc[0] + "," + loc[1] + ".");
		}
	}

	protected void calcDamageRange() {
		int fcCount = 0;
		if (fc != null) {
			fcCount = 1;
		}
		double lvl = Math.pow(1.45, level);
		double upg = (1f +
				TowerUpgradeDamage.upgradeEffectDamage * upgradeDamage.size() +
				TowerFireControl.upgradeEffectDamage * fcCount);
		damageCurrent = Math.round(damageBase * lvl * upg);
		damageText = "" +
				TowerBase.form.format(damageBase) + "*" +
				TowerBase.form.format(lvl) + "*" +
				TowerBase.form.format(upg) + "=" +
				TowerBase.form.format(this.damageCurrent);

		lvl = (float) Math.pow(1.07, level);
		upg = (1f +
				TowerUpgradeRange.upgradeEffectRange * upgradeRange.size() +
				TowerFireControl.upgradeEffectRange * fcCount);
		rangeCurrent = (float) (rangeBase * lvl * upg);
		rangeText = "" +
				TowerBase.form.format(rangeBase) + "*" +
				TowerBase.form.format(lvl) + "*" +
				TowerBase.form.format(upg) + "=" +
				TowerBase.form.format(this.rangeCurrent);

		rangePaint = rangeCurrent * paintScale;
		rangeCurrent2 = rangeCurrent * rangeCurrent;
		rangeCircle = new Ellipse2D.Float(-rangePaint, -rangePaint, rangePaint * 2, rangePaint * 2);
	}

	abstract protected void generateShapes();

	public String getSvgName() {
		return null;
	}

	@Override
	public void setSelected(boolean selected) {
		this.selected = selected;
	}

	/* Non specific enemy finders findEnemiesInRange and findEnemyClosest
	 * There are used for bouncing projectiles and splash damage
	 * These can not be deferred to a firecontrol tower.
	 */
	protected Creep[] findEnemiesInRange(float x, float y, float range) {
		ArrayList<Creep> tempEnemies = new ArrayList<Creep>();
		Collection<Creep> targets = context.getTargets(owner);

		float r2 = range * range;
		float dx, dy, d2;

		for (Creep e : targets) {
			if (e.validTarget()) {
				dx = e.getX() - x;
				dy = e.getY() - y;
				d2 = dx * dx + dy * dy;
				if (d2 < r2) {
					tempEnemies.add(e);
				}
			}
		}
		Creep[] retVal = new Creep[tempEnemies.size()];
		retVal = tempEnemies.toArray(retVal);
		return retVal;
	}

	protected Creep findEnemyClosest(float x, float y, float range, Creep[] not) {
		Collection<Creep> targets = this.context.getTargets(this.owner);
		Creep foundEnemy = null;
		float distance2Min = range * range;
		float distance2;
		float dx, dy;

		boolean found;
		for (Creep e : targets) {
			found = false;
			for (int n = 0; n < not.length; n++) {
				if (not[n] == e) {
					found = true;
					continue;
				}
			}
			if (!found && e.validTarget()) {
				dx = e.getX() - x;
				dy = e.getY() - y;
				distance2 = dx * dx + dy * dy;
				if (distance2 < distance2Min) {
					distance2Min = distance2;
					foundEnemy = e;
				} else if (distance2 == distance2Min) {
					if (compareWeak.compare(e, foundEnemy) > 0) {
						distance2Min = distance2;
						foundEnemy = e;
					}
				}
			}
		}
		return foundEnemy;
	}

	/* Specific enemy finders
	 * These can not be deferred to a firecontrol tower.
	 */
	protected Creep[] findEnemiesFastest(float x, float y, float range, int number, boolean exShielded) {
		return this.findEnemiesFastest(x, y, range, number, new Creep[0], exShielded);
	}

	protected Creep[] findEnemiesFastest(float x, float y, float range, Creep[] preferred, int number, boolean exShielded) {
		ArrayList<Creep> found = new ArrayList<Creep>(number);
		float r2 = range * range;
		float dx, dy, d2;
		Creep pe;
		if (preferred != null) {
			for (int p = 0; p < preferred.length; p++) {
				pe = preferred[p];
				if (pe.validTarget()) {
					dx = pe.getX() - x;
					dy = pe.getY() - y;
					d2 = dx * dx + dy * dy;
					if (d2 < r2) {
						found.add(pe);
					}
				}
			}
		}
		Creep[] not = new Creep[found.size()];
		not = found.toArray(not);
		if (number - not.length > 0) {
			Creep[] extra = this.findEnemiesFastest(x, y, range, number - not.length, not, exShielded);
			for (int e = 0; e < extra.length; e++) {
				found.add(extra[e]);
			}
		}
		Creep[] retval = new Creep[found.size()];
		return found.toArray(retval);
	}

	protected Creep[] findEnemiesFastest(float x, float y, float range, int number, Creep[] not, boolean exShielded) {
		return CreepCompare.findLargestSet(
				context.getTargets(owner),
				number,
				x, y, range,
				compareFast,
				not,
				exShielded);
	}

	/* Specific enemy finders
	 * These can be deferred to a firecontrol tower.
	 */
	public Creep findEnemy(Creep preferedEnemy, boolean exShielded) {
		if (this.retarget) {
			preferedEnemy = null;
			this.retarget = false;
		}
		if (this.fc != null) {
			return this.fc.findEnemy(this, preferedEnemy, exShielded);
		}

		switch (this.mode) {
			case 0:
				return this.findEnemyClosest(preferedEnemy, exShielded);
			case 1:
				return this.findEnemyHardest(preferedEnemy, exShielded);
			case 2:
				return this.findEnemyWeakest(preferedEnemy, exShielded);
			case 3:
				return this.findEnemyFurthest(preferedEnemy, exShielded);
			default:
				System.err.println("TowerBase::FindEnemy: Unknown mode " + this.mode);
		}
		return null;
	}

	private Creep findEnemyClosest(Creep preferedEnemy, boolean exShielded) {
		Collection<Creep> targets = this.context.getTargets(this.owner);
		Creep foundEnemy = null;
		float distance2Min = 1E10f;
		float distance2;
		float dx, dy;

		if (preferedEnemy != null) {
			if (preferedEnemy.validTarget() && !(exShielded && preferedEnemy.isShielded())) {
				dx = preferedEnemy.getX() - this.xFloat;
				dy = preferedEnemy.getY() - this.yFloat;
				distance2 = dx * dx + dy * dy;
				if (distance2 < this.rangeCurrent2) {
					return preferedEnemy;
				}
			}
		}
		for (Creep e : targets) {
			if (e.validTarget() && !(exShielded && e.isShielded())) {
				dx = e.getX() - this.xFloat;
				dy = e.getY() - this.yFloat;
				distance2 = dx * dx + dy * dy;
				if (distance2 < this.rangeCurrent2 && distance2 < distance2Min) {
					distance2Min = distance2;
					foundEnemy = e;
				} else if (distance2 == distance2Min) {
					if (compareWeak.compare(e, foundEnemy) > 0) {
						distance2Min = distance2;
						foundEnemy = e;
					}
				}
			}
		}
		return foundEnemy;
	}

	private Creep findEnemyWeakest(Creep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(context.getTargets(owner), xFloat, yFloat, rangeCurrent, compareWeak, preferedEnemy, exShielded);
	}

	private Creep findEnemyHardest(Creep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(context.getTargets(owner), xFloat, yFloat, rangeCurrent, compareHard, preferedEnemy, exShielded);
	}

	private Creep findEnemyFurthest(Creep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(context.getTargets(owner), xFloat, yFloat, rangeCurrent, compareFar, preferedEnemy, exShielded);
	}

	private void setLocation() {
		if (this.hex != null) {
			float[] realLocation = this.hex.getFloatLocation();
			this.xFloat = realLocation[0];
			this.yFloat = realLocation[1];
		} else {
			this.xFloat = 1f;
			this.yFloat = 1f;
		}
	}

	@Override
	public void resetScale() {
		if (context instanceof ContextNormal) {
			cn = (ContextNormal) context;
			ScaledCanvas sc = cn.getScaledCanvas();
			this.rangeColor = cn.colors.getColorRangeCircle();

			paintScale = sc.getPaintScale();
			strokeScale = sc.getStrokeScale();
			offsetX = sc.getOffsetX();
			offsetY = sc.getOffsetY();
			hexPaintWidth = sc.getHexPaintWidth();

			calcDamageRange();
			generateShapes();

			float max = Context.actionDelay;
			this.queueShapes = new Shape[Context.actionDelay * 2];
			for (int i = 0; i < this.queueShapes.length; i++) {
				queueShapes[i] = new Arc2D.Float(-this.paintScale, -this.paintScale, this.paintScale * 2, this.paintScale * 2, 0, 360f * i / (max - 1), Arc2D.OPEN);
			}

			this.xPaint = this.xFloat * this.paintScale + sc.getOffsetX();
			this.yPaint = this.yFloat * this.paintScale + sc.getOffsetY();
			atTranslate.setToIdentity();
			atTranslate.translate(this.xPaint, this.yPaint);

			this.rangeStroke = new BasicStroke(1.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

			this.levelFont = new Font(Font.DIALOG, Font.PLAIN, (int) (0.40 * this.paintScale));
			this.levelLocation[0] = -0.95f * hexPaintWidth;
			this.levelLocation[1] = 0.5f * paintScale;

			this.bodySVGName = this.getSvgName();
			if (this.bodySVGName != null) {
				this.bodyUsesSVG = true;
				this.bodyScale = paintScale * 1.5f;
				atCorrect.setToIdentity();
				atCorrect.setToTranslation(-this.bodyScale / 2, -this.bodyScale / 2);
			}
		}
	}

	public void paintQueue(Graphics2D g2, int gameTime) {
		g2.setColor(rangeColor);
		g2.setStroke(rangeStroke);
		for (TowerAction a : actionQueue) {
			int delta = a.gameTime - gameTime;
			if (delta >= 0 && delta < queueShapes.length) {
				g2.draw(queueShapes[delta]);
			}
		}
	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
		if (cn == null) {
			System.err.println("TowerBase::paint: Painting without normal context!");
		}
		Stroke defaultStroke = g2.getStroke();
		AffineTransform saveXform = g2.getTransform();

		g2.transform(atTranslate);
		g2.setColor(this.lineColor);
		g2.setFont(this.levelFont);
		g2.drawString("" + (this.level + 1), this.levelLocation[0], this.levelLocation[1]);

		if (this.selected) {
			g2.setColor(this.rangeColor);
			g2.draw(this.rangeCircle);
		}

		if (this.bodyUsesSVG) {
			AffineTransform saveXform2 = g2.getTransform();
			g2.transform(this.atCorrect);
			cn.renderSvgImage(this.bodySVGName, g2, (int) this.bodyScale, (int) this.bodyScale);
			g2.setTransform(saveXform2);
		} else {
			if (shapes[0] != null) {
				for (int i = 0; i < this.shapes.length; i++) {
					g2.setColor(this.shapeColor[i]);
					switch (this.shapeAction[i]) {
						case 0:
							g2.setStroke(this.strokes[i]);
							g2.fill(this.shapes[i]);
							g2.draw(this.shapes[i]);
							break;
						case 1:
							g2.setStroke(this.strokes[i]);
							g2.draw(this.shapes[i]);
							break;
						case 2:
							g2.fill(this.shapes[i]);
							break;
					}
				}
			}
		}
		this.paintQueue(g2, gameTime);

		g2.setTransform(saveXform);
		g2.setStroke(defaultStroke);

	}

	@Override
	public Hex getHex() {
		return hex;
	}

	@Override
	public void setHex(Hex hex) {
		this.hex = hex;
	}

	@Override
	public void setSetting(JsonTower.optionKey key, String value) {
		switch (key) {
			case maxlevel:
				levelMax = Integer.parseInt(value);
				System.out.println("maxLevel set to " + levelMax);
				break;
			default:
				System.err.println("TowerHome::setSetting: Unknown key: " + key);
				break;
		}
	}

	@Override
	public void registerTower(Tower t) {
		if (t != this && !selling) {
			switch (t.getType()) {
				case firecontrol:
					if (type != TowerFactory.type.upgradeDamage && type != TowerFactory.type.upgradeRange && type != TowerFactory.type.firecontrol) {
						if (fc != null) {
							fc.removeClient(this);
						}
						fc = (TowerFireControl) t;
						fc.addClient(this);
						calcDamageRange();
					}
					break;
				case upgradeDamage:
					if (type != TowerFactory.type.upgradeDamage && type != TowerFactory.type.upgradeRange && type != TowerFactory.type.firecontrol) {
						TowerUpgradeDamage tud = (TowerUpgradeDamage) t;
						if (!upgradeDamage.contains(tud)) {
							upgradeDamage.add(tud);
							tud.addClient(this);
							calcDamageRange();
						}
					}
					break;
				case upgradeRange:
					TowerUpgradeRange tur = (TowerUpgradeRange) t;
					if (!upgradeRange.contains(tur)) {
						upgradeRange.add(tur);
						tur.addClient(this);
						calcDamageRange();
					}
					break;
			}
		}
	}

	@Override
	public void unregisterTower(Tower t) {
		switch (t.getType()) {
			case firecontrol:
				if (this.fc != null) {
					this.fc.removeClient(this);
					this.fc = null;
				}
				break;
			case upgradeDamage:
				TowerUpgradeDamage tud = (TowerUpgradeDamage) t;
				this.upgradeDamage.remove(tud);
				tud.removeClient(this);
				break;
			case upgradeRange:
				TowerUpgradeRange tur = (TowerUpgradeRange) t;
				this.upgradeRange.remove(tur);
				tur.removeClient(this);
				break;
		}
		this.calcDamageRange();
	}

	@Override
	public void doCleanup() {
		this.selling = true;
		if (this.fc != null) {
			this.fc.removeClient(this);
			this.fc = null;
		}
		int i;
		for (i = this.upgradeDamage.size() - 1; i >= 0; i--) {
			TowerUpgradeDamage tud = this.upgradeDamage.remove(i);
			tud.removeClient(this);
		}
		for (i = this.upgradeRange.size() - 1; i >= 0; i--) {
			TowerUpgradeRange tur = this.upgradeRange.remove(i);
			tur.removeClient(this);
		}
	}

	@Override
	public TowerFactory.type getType() {
		return type;
	}

	protected void makeButtons() {
		if (cn != null) {
			PanelTowerSettingsSingle p = new PanelTowerSettingsSingle(this, cn);
			p.setModes(1, modes);
			p.setMode(1, mode, true);
			pts = p;
		}
	}

	@Override
	public PanelTowerSettings getButtons() {
		if (pts == null) {
			makeButtons();
		}
		return this.pts;
	}

	@Override
	public void addTowerChangeListener(TowerChangeListener l) {
		this.towerChangeListeners.add(l);
	}

	@Override
	public void removeTowerChangeListener(TowerChangeListener l) {
		this.towerChangeListeners.remove(l);
	}

	protected void fireTowerChangedEvent() {
		for (int i = 0; i < this.towerChangeListeners.size(); i++) {
			this.towerChangeListeners.get(i).towerChanged();
		}
	}
}
